package lock.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

import static lock.redis.Constant.LOCK_TIMEOUT_SECONDS;
import static lock.redis.Constant.WATCH_DOG_INTERVAL_SECONDS;

/**
 * 单redis加锁v1
 * 单命令加锁 nx px
 * lua脚本释放锁 判断是否持有锁，再释放
 */
public class SingleRedisLock implements RedisLock {
    Jedis client;
    String lockValue;
    String unLockScript = """
            if redis.call('get', KEYS[1]) == ARGV[1] then 
                return redis.call('del', KEYS[1])
            else 
                return 0 
            end
            """;

    String watchDogScript = """
            if redis.call('get', KEYS[1]) == ARGV[1] then 
                return redis.call('expire', KEYS[1], ARGV[2])
            else 
                return 0 
            end
            """;

    AtomicBoolean locked = new AtomicBoolean(false);

    public SingleRedisLock(Jedis client) {
        this.client = client;
        genLockKey();
    }

    void genLockKey() {
        this.lockValue = UUID.randomUUID() + ":" + Thread.currentThread().threadId();
    }

    public boolean lock(String key) {
        return lock(key, LOCK_TIMEOUT_SECONDS);
    }

    public boolean lock(String key, long timeOut) {
        SetParams params = new SetParams().nx();
        if (timeOut > 0L) {
            params.px(timeOut);
        }
        String result = client.set(key, lockValue, params);
        boolean ok = "OK".equals(result);
        if (ok) {
            locked.set(true);
            startWatchDog(key);
        }
        return ok;
    }

    private void startWatchDog(String key) {
        new Thread(() -> {
            while (locked.get()) {
                try {
                    Thread.sleep(WATCH_DOG_INTERVAL_SECONDS * 1000L);
                    client.eval(watchDogScript, Collections.singletonList(key), List.of(lockValue, String.valueOf(LOCK_TIMEOUT_SECONDS)));
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }

    public boolean unlock(String key) {
        Object result = client.eval(unLockScript, Collections.singletonList(key), Collections.singletonList(lockValue));
        return result != null && (long) result == 1;
    }

    @Override
    public void close() {
        client.close();
    }
}